//
// This file is part of the Frenetic Game Engine, created by Frenetic LLC.
// This code is Copyright (C) Frenetic LLC under the terms of a strict license.
// See README.md or LICENSE.txt in the FreneticGameEngine source root for the contents of the license.
// If neither of these are available, assume that neither you nor anyone other than the copyright holder
// hold any right or permission to use this software until such time as the official license is identified.
//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL4;
using FGECore;
using FGECore.CoreSystems;
using FGECore.MathHelpers;
using FreneticUtilities.FreneticExtensions;
using FGECore.UtilitySystems;
using FreneticUtilities.FreneticToolkit;
using FGEGraphics.ClientSystem;

namespace FGEGraphics.GraphicsHelpers
{
    /// <summary>
    /// Represents a Vertex Buffer/Array Object set.
    /// </summary>
    public class Renderable
    {
        /// <summary>
        /// A structure of the internal GPU-side data for this <see cref="Renderable"/>.
        /// </summary>
        public struct InternalData
        {
            /// <summary>
            /// The vertices buffer.
            /// </summary>
            public uint VertexVBO;

            /// <summary>
            /// The indices buffer.
            /// </summary>
            public uint IndexVBO;

            /// <summary>
            /// The normals buffer.
            /// </summary>
            public uint NormalVBO;

            /// <summary>
            /// The texture coordinates buffer.
            /// </summary>
            public uint TexCoordVBO;

            /// <summary>
            /// The colors buffer.
            /// </summary>
            public uint ColorVBO;

            /// <summary>
            /// The tangents buffer.
            /// </summary>
            public uint TangentVBO;

            /// <summary>
            /// The bone IDs buffer.
            /// </summary>
            public uint BoneIDVBO;

            /// <summary>
            /// The bone weights buffer.
            /// </summary>
            public uint BoneWeightVBO;

            /// <summary>
            /// The bone IDs (set 2) buffer.
            /// </summary>
            public uint BoneID2VBO;

            /// <summary>
            /// The bone weights (set 2) buffer.
            /// </summary>
            public uint BoneWeight2VBO;

            /// <summary>
            /// The internal main Vertex Array Object.
            /// </summary>
            public int VAO;

            /// <summary>
            /// How much VRAM this <see cref="Renderable"/> would consume at last generation.
            /// </summary>
            public long LastVRAM;

            /// <summary>
            /// Whether this <see cref="Renderable"/> has colors.
            /// </summary>
            public bool HasColors;

            /// <summary>
            /// Whether this <see cref="Renderable"/> has bones.
            /// </summary>
            public bool HasBones;

            /// <summary>
            /// The number of indices in this <see cref="Renderable"/>.
            /// </summary>
            public int IndexCount;

            /// <summary>
            /// What buffer mode to use.
            /// </summary>
            public BufferUsageHint BufferMode;
        }

        /// <summary>
        /// The internal (GPU) data for this <see cref="Renderable"/>.
        /// </summary>
        public InternalData Internal = new InternalData() { VAO = -1, BufferMode = BufferUsageHint.StaticDraw };
        
        /// <summary>
        /// The primary texture.
        /// </summary>
        public Texture Tex;
        
        /// <summary>
        /// The specular texture.
        /// </summary>
        public Texture Tex_Specular;
        
        /// <summary>
        /// The reflectivity texture.
        /// </summary>
        public Texture Tex_Reflectivity;

        /// <summary>
        /// The normal texture.
        /// </summary>
        public Texture Tex_Normal;

        /// <summary>
        /// Represents a <see cref="Renderable"/> builder type object.
        /// </summary>
        public abstract class Builder
        {
            /// <summary>
            /// Generates a VBO from this builder.
            /// </summary>
            /// <returns>The generated VBO.</returns>
            public Renderable Generate()
            {
                Renderable vbo = new Renderable();
                vbo.GenerateVBO(this);
                return vbo;
            }
        }

        /// <summary>
        /// A way to build a <see cref="Renderable"/> from lists.
        /// </summary>
        public class ListBuilder : Builder
        {
            /// <summary>
            /// The position vertices.
            /// </summary>
            public List<Vector3> Vertices;

            /// <summary>
            /// The indices.
            /// </summary>
            public List<uint> Indices;

            /// <summary>
            /// The normals.
            /// </summary>
            public List<Vector3> Normals;

            /// <summary>
            /// The normal tangents.
            /// </summary>
            public List<Vector3> Tangents;

            /// <summary>
            /// The texture coordinates.
            /// </summary>
            public List<Vector3> TexCoords;

            /// <summary>
            /// The colors.
            /// </summary>
            public List<Vector4> Colors;

            /// <summary>
            /// The bone weight IDs.
            /// </summary>
            public List<Vector4> BoneIDs;

            /// <summary>
            /// The bone weight levels.
            /// </summary>
            public List<Vector4> BoneWeights;

            /// <summary>
            /// The second bone weight IDs.
            /// </summary>
            public List<Vector4> BoneIDs2;

            /// <summary>
            /// The second bone weight levels.
            /// </summary>
            public List<Vector4> BoneWeights2;

            /// <summary>
            /// Adds an empty bone info (1 vertex worth of pure zeroes).
            /// </summary>
            public void AddEmptyBoneInfo()
            {
                BoneIDs.Add(Vector4.Zero);
                BoneWeights.Add(Vector4.Zero);
                BoneIDs2.Add(Vector4.Zero);
                BoneWeights2.Add(Vector4.Zero);
            }

            /// <summary>
            /// Sets the capacity of all lists.
            /// </summary>
            /// <param name="capacity">The capacity to set.</param>
            public void SetCapacity(int capacity)
            {
                Vertices.Capacity = capacity;
                Indices.Capacity = capacity;
                Normals.Capacity = capacity;
                Tangents.Capacity = capacity;
                TexCoords.Capacity = capacity;
                Colors.Capacity = capacity;
                BoneIDs.Capacity = capacity;
                BoneWeights.Capacity = capacity;
                BoneIDs2.Capacity = capacity;
                BoneWeights2.Capacity = capacity;
            }

            /// <summary>
            /// Helper to add an axis-aligned side to this <see cref="Renderable"/> builder.
            /// </summary>
            /// <param name="normal">The normal.</param>
            /// <param name="tc">The texture coordinates.</param>
            /// <param name="offs">Whether to offset.</param>
            /// <param name="texf">The texture ID.</param>
            public void AddSide(Location normal, TextureCoordinates tc, bool offs = false, float texf = 0)
            {
                if (Vertices.Capacity < 6)
                {
                    SetCapacity(12);
                }
                // TODO: IMPROVE! Or discard?!
                for (int i = 0; i < 6; i++)
                {
                    Normals.Add(normal.ToOpenTK());
                    Colors.Add(new Vector4(1f, 1f, 1f, 1f));
                    Indices.Add((uint)Indices.Count);
                    AddEmptyBoneInfo();
                }
                float aX = (tc.XFlip ? 1 : 0) + tc.XShift;
                float aY = (tc.YFlip ? 1 : 0) + tc.YShift;
                float bX = (tc.XFlip ? 0 : 1) * tc.XScale + tc.XShift;
                float bY = (tc.YFlip ? 1 : 0) + tc.YShift;
                float cX = (tc.XFlip ? 0 : 1) * tc.XScale + tc.XShift;
                float cY = (tc.YFlip ? 0 : 1) * tc.YScale + tc.YShift;
                float dX = (tc.XFlip ? 1 : 0) + tc.XShift;
                float dY = (tc.YFlip ? 0 : 1) * tc.YScale + tc.YShift;
                float zero = offs ? -0.5f : -1; // Sssh
                float one = offs ? 0.5f : 1;
                if (normal.Z == 1)
                {
                    // T1
                    TexCoords.Add(new Vector3(bX, bY, texf));
                    Vertices.Add(new Vector3(zero, zero, one));
                    TexCoords.Add(new Vector3(cX, cY, texf));
                    Vertices.Add(new Vector3(zero, one, one));
                    TexCoords.Add(new Vector3(dX, dY, texf));
                    Vertices.Add(new Vector3(one, one, one));
                    // T2
                    TexCoords.Add(new Vector3(bX, bY, texf));
                    Vertices.Add(new Vector3(zero, zero, one));
                    TexCoords.Add(new Vector3(dX, dY, texf));
                    Vertices.Add(new Vector3(one, one, one));
                    TexCoords.Add(new Vector3(aX, aY, texf));
                    Vertices.Add(new Vector3(one, zero, one));
                }
                else if (normal.Z == -1)
                {
                    // T1
                    TexCoords.Add(new Vector3(cX, cY, texf));
                    Vertices.Add(new Vector3(one, one, zero));
                    TexCoords.Add(new Vector3(dX, dY, texf));
                    Vertices.Add(new Vector3(zero, one, zero));
                    TexCoords.Add(new Vector3(aX, aY, texf));
                    Vertices.Add(new Vector3(zero, zero, zero));
                    // T2
                    TexCoords.Add(new Vector3(bX, bY, texf));
                    Vertices.Add(new Vector3(one, zero, zero));
                    TexCoords.Add(new Vector3(cX, cY, texf));
                    Vertices.Add(new Vector3(one, one, zero));
                    TexCoords.Add(new Vector3(aX, aY, texf));
                    Vertices.Add(new Vector3(zero, zero, zero));
                }
                else if (normal.X == 1)
                {
                    // T1
                    TexCoords.Add(new Vector3(bX, bY, texf));
                    Vertices.Add(new Vector3(one, one, one));
                    TexCoords.Add(new Vector3(cX, cY, texf));
                    Vertices.Add(new Vector3(one, one, zero));
                    TexCoords.Add(new Vector3(dX, dY, texf));
                    Vertices.Add(new Vector3(one, zero, zero));
                    // T2
                    TexCoords.Add(new Vector3(aX, aY, texf));
                    Vertices.Add(new Vector3(one, zero, one));
                    TexCoords.Add(new Vector3(bX, bY, texf));
                    Vertices.Add(new Vector3(one, one, one));
                    TexCoords.Add(new Vector3(dX, dY, texf));
                    Vertices.Add(new Vector3(one, zero, zero));
                }
                else if (normal.X == -1)
                {
                    // T1
                    TexCoords.Add(new Vector3(cX, cY, texf));
                    Vertices.Add(new Vector3(zero, zero, zero));
                    TexCoords.Add(new Vector3(dX, dY, texf));
                    Vertices.Add(new Vector3(zero, one, zero));
                    TexCoords.Add(new Vector3(aX, aY, texf));
                    Vertices.Add(new Vector3(zero, one, one));
                    // T2
                    TexCoords.Add(new Vector3(cX, cY, texf));
                    Vertices.Add(new Vector3(zero, zero, zero));
                    TexCoords.Add(new Vector3(aX, aY, texf));
                    Vertices.Add(new Vector3(zero, one, one));
                    TexCoords.Add(new Vector3(bX, bY, texf));
                    Vertices.Add(new Vector3(zero, zero, one));
                }
                else if (normal.Y == 1)
                {
                    // T1
                    TexCoords.Add(new Vector3(aX, aY, texf));
                    Vertices.Add(new Vector3(one, one, one));
                    TexCoords.Add(new Vector3(bX, bY, texf));
                    Vertices.Add(new Vector3(zero, one, one));
                    TexCoords.Add(new Vector3(cX, cY, texf));
                    Vertices.Add(new Vector3(zero, one, zero));
                    // T2
                    TexCoords.Add(new Vector3(dX, dY, texf));
                    Vertices.Add(new Vector3(one, one, zero));
                    TexCoords.Add(new Vector3(aX, aY, texf));
                    Vertices.Add(new Vector3(one, one, one));
                    TexCoords.Add(new Vector3(cX, cY, texf));
                    Vertices.Add(new Vector3(zero, one, zero));
                }
                else if (normal.Y == -1)
                {
                    // T1
                    TexCoords.Add(new Vector3(dX, dY, texf));
                    Vertices.Add(new Vector3(zero, zero, zero));
                    TexCoords.Add(new Vector3(aX, aY, texf));
                    Vertices.Add(new Vector3(zero, zero, one));
                    TexCoords.Add(new Vector3(bX, bY, texf));
                    Vertices.Add(new Vector3(one, zero, one));
                    // T2
                    TexCoords.Add(new Vector3(dX, dY, texf));
                    Vertices.Add(new Vector3(zero, zero, zero));
                    TexCoords.Add(new Vector3(bX, bY, texf));
                    Vertices.Add(new Vector3(one, zero, one));
                    TexCoords.Add(new Vector3(cX, cY, texf));
                    Vertices.Add(new Vector3(one, zero, zero));
                }
                else
                {
                    throw new Exception("Lazy code can't handle unique normals! Only axis-aligned, normalized normals!");
                }
            }

            /// <summary>
            /// Prepare the <see cref="Renderable"/> builder's lists to be added to, for 2D usage.
            /// Does not prepare bones or tangents.
            /// </summary>
            /// <param name="capacity">The initial capacity.</param>
            public void Prepare2D(int capacity = 10)
            {
                Vertices = new List<Vector3>(capacity);
                Indices = new List<uint>(capacity);
                Normals = new List<Vector3>(capacity);
                TexCoords = new List<Vector3>(capacity);
                Colors = new List<Vector4>(capacity);
            }

            /// <summary>
            /// Prepare the <see cref="Renderable"/> builder's lists to be added to.
            /// Does not prepare tangents.
            /// </summary>
            /// <param name="capacity">The initial capacity.</param>
            public void Prepare(int capacity = 10)
            {
                Vertices = new List<Vector3>(capacity);
                Indices = new List<uint>(capacity);
                Normals = new List<Vector3>(capacity);
                TexCoords = new List<Vector3>(capacity);
                Colors = new List<Vector4>(capacity);
                BoneIDs = new List<Vector4>(capacity);
                BoneWeights = new List<Vector4>(capacity);
                BoneIDs2 = new List<Vector4>(capacity);
                BoneWeights2 = new List<Vector4>(capacity);
            }
        }

        /// <summary>
        /// A way to build a <see cref="Renderable"/> from arrays.
        /// </summary>
        public class ArrayBuilder : Builder
        {
            /// <summary>
            /// The position vertices.
            /// </summary>
            public Vector3[] Vertices;

            /// <summary>
            /// The indices.
            /// </summary>
            public uint[] Indices;

            /// <summary>
            /// The normals.
            /// </summary>
            public Vector3[] Normals;

            /// <summary>
            /// The normal tangents.
            /// </summary>
            public Vector3[] Tangents;

            /// <summary>
            /// The texture coordinates.
            /// </summary>
            public Vector3[] TexCoords;

            /// <summary>
            /// The colors.
            /// </summary>
            public Vector4[] Colors;

            /// <summary>
            /// The bone weight IDs.
            /// </summary>
            public Vector4[] BoneIDs;

            /// <summary>
            /// The bone weight levels.
            /// </summary>
            public Vector4[] BoneWeights;

            /// <summary>
            /// The second bone weight IDs.
            /// </summary>
            public Vector4[] BoneIDs2;

            /// <summary>
            /// The second bone weight levels.
            /// </summary>
            public Vector4[] BoneWeights2;

            /// <summary>
            /// Sets the bone info for a given vertex index to zero.
            /// </summary>
            /// <param name="index">The index in the arrays of the vertex to zero the bones for.</param>
            public void ZeroBoneInfo(uint index)
            {
                BoneIDs[index] = Vector4.Zero;
                BoneWeights[index] = Vector4.Zero;
                BoneIDs2[index] = Vector4.Zero;
                BoneWeights2[index] = Vector4.Zero;
            }

            /// <summary>
            /// Prepare the <see cref="Renderable"/> builder's arrays to be filled with values.
            /// Does not generate tangents.
            /// </summary>
            /// <param name="vertexCount">How many vertices.</param>
            /// <param name="indexCount">How many indices.</param>
            public void Prepare(int vertexCount, int indexCount)
            {
                Vertices = new Vector3[vertexCount];
                Indices = new uint[indexCount];
                Normals = new Vector3[vertexCount];
                TexCoords = new Vector3[vertexCount];
                Colors = new Vector4[vertexCount];
                BoneIDs = new Vector4[vertexCount];
                BoneWeights = new Vector4[vertexCount];
                BoneIDs2 = new Vector4[vertexCount];
                BoneWeights2 = new Vector4[vertexCount];
            }

            /// <summary>
            /// Prepare the <see cref="Renderable"/> builder's arrays to be filled with values for 2D usage.
            /// Does not generate bones or tangents.
            /// </summary>
            /// <param name="vertexCount">How many vertices.</param>
            /// <param name="indexCount">How many indices.</param>
            public void Prepare2D(int vertexCount, int indexCount)
            {
                Vertices = new Vector3[vertexCount];
                Indices = new uint[indexCount];
                Normals = new Vector3[vertexCount];
                TexCoords = new Vector3[vertexCount];
                Colors = new Vector4[vertexCount];
            }
        }

        /// <summary>
        /// Gets a somewhat accurate amount of vRAM usage, if any.
        /// </summary>
        /// <returns>The vRAM usage.</returns>
        public long GetVRAMUsage()
        {
            if (Generated)
            {
                return Internal.LastVRAM;
            }
            return 0;
        }

        /// <summary>
        /// Whether the VBO has been generated on the GPU.
        /// </summary>
        public bool Generated = false;

        /// <summary>
        /// Destroys GPU-side data.
        /// </summary>
        public void Destroy()
        {
            if (Generated)
            {
                GL.DeleteVertexArray(Internal.VAO);
                GL.DeleteBuffer(Internal.VertexVBO);
                GL.DeleteBuffer(Internal.IndexVBO);
                GL.DeleteBuffer(Internal.NormalVBO);
                GL.DeleteBuffer(Internal.TexCoordVBO);
                GL.DeleteBuffer(Internal.TangentVBO);
                if (Internal.HasColors)
                {
                    GL.DeleteBuffer(Internal.ColorVBO);
                    Internal.HasColors = false;
                }
                if (Internal.HasBones)
                {
                    GL.DeleteBuffer(Internal.BoneIDVBO);
                    GL.DeleteBuffer(Internal.BoneWeightVBO);
                    GL.DeleteBuffer(Internal.BoneID2VBO);
                    GL.DeleteBuffer(Internal.BoneWeight2VBO);
                    Internal.HasBones = false;
                }
                Generated = false;
            }
        }

        /// <summary>
        /// Gets the normal tangents for a VBO setup.
        /// </summary>
        /// <param name="vecs">The vertices.</param>
        /// <param name="norms">The normals.</param>
        /// <param name="texs">The texture coordinates.</param>
        /// <returns>The tangent set.</returns>
        public static Vector3[] TangentsFor(Vector3[] vecs, Vector3[] norms, Vector3[] texs)
        {
            Vector3[] tangs = new Vector3[vecs.Length];
            if (vecs.Length != norms.Length || texs.Length != vecs.Length || (vecs.Length % 3) != 0)
            {
                for (int i = 0; i < tangs.Length; i++)
                {
                    tangs[i] = new Vector3(0, 0, 0);
                }
                return tangs;
            }
            for (int i = 0; i < vecs.Length; i += 3)
            {
                Vector3 v1 = vecs[i];
                Vector3 dv1 = vecs[i + 1] - v1;
                Vector3 dv2 = vecs[i + 2] - v1;
                Vector3 t1 = texs[i];
                Vector3 dt1 = texs[i + 1] - t1;
                Vector3 dt2 = texs[i + 2] - t1;
                Vector3 tangent = (dv1 * dt2.Y - dv2 * dt1.Y) / (dt1.X * dt2.Y - dt1.Y * dt2.X);
                Vector3 normal = norms[i];
                tangent = (tangent - normal * Vector3.Dot(normal, tangent)).Normalized(); // TODO: Necessity of this correction?
                tangs[i] = tangent;
                tangs[i + 1] = tangent;
                tangs[i + 2] = tangent;
            }
            return tangs;
        }

        /// <summary>
        /// Gets the normal tangents for a VBO setup.
        /// </summary>
        /// <param name="vecs">The vertices.</param>
        /// <param name="norms">The normals.</param>
        /// <param name="texs">The texture coordinates.</param>
        /// <returns>The tangent set.</returns>
        public static Vector3[] TangentsFor(Vector3[] vecs, Vector3[] norms, Vector2[] texs)
        {
            Vector3[] tangs = new Vector3[vecs.Length];
            if (vecs.Length != norms.Length || texs.Length != vecs.Length || (vecs.Length % 3) != 0)
            {
                for (int i = 0; i < tangs.Length; i++)
                {
                    tangs[i] = new Vector3(0, 0, 0);
                }
                return tangs;
            }
            for (int i = 0; i < vecs.Length; i += 3)
            {
                Vector3 v1 = vecs[i];
                Vector3 dv1 = vecs[i + 1] - v1;
                Vector3 dv2 = vecs[i + 2] - v1;
                Vector2 t1 = texs[i];
                Vector2 dt1 = texs[i + 1] - t1;
                Vector2 dt2 = texs[i + 2] - t1;
                Vector3 tangent = (dv1 * dt2.Y - dv2 * dt1.Y) / (dt1.X * dt2.Y - dt1.Y * dt2.X);
                Vector3 normal = norms[i];
                tangent = (tangent - normal * Vector3.Dot(normal, tangent)).Normalized(); // TODO: Necessity of this correction?
                tangs[i] = tangent;
                tangs[i + 1] = tangent;
                tangs[i + 2] = tangent;
            }
            return tangs;
        }
        
        /// <summary>
        /// Generate this <see cref="Renderable"/> to the GPU.
        /// </summary>
        /// <param name="builder">The builder to use.</param>
        public void GenerateVBO(Builder builder)
        {
            if (Generated)
            {
                Destroy();
            }
            GL.BindVertexArray(0);
            uint[] inds;
            Vector3[] vecs;
            Vector3[] norms;
            Vector3[] texs;
            Vector3[] tangs;
            Vector4[] cols;
            Vector4[] ids = null;
            Vector4[] weights = null;
            Vector4[] ids2 = null;
            Vector4[] weights2 = null;
            if (builder is ListBuilder buildList)
            {
                if (buildList.Vertices == null)
                {
                    SysConsole.Output(OutputType.ERROR, "Failed to generate VBO, null vertices!");
                    return;
                }
                vecs = buildList.Vertices.ToArray();
                inds = buildList.Indices.ToArray();
                norms = buildList.Normals.ToArray();
                texs = buildList.TexCoords.ToArray();
                tangs = buildList.Tangents != null ? buildList.Tangents.ToArray() : TangentsFor(vecs, norms, texs);
                cols = buildList.Colors?.ToArray();
                if (buildList.BoneIDs != null)
                {
                    Internal.HasBones = true;
                    ids = buildList.BoneIDs.ToArray();
                    weights = buildList.BoneWeights.ToArray();
                    ids2 = buildList.BoneIDs2.ToArray();
                    weights2 = buildList.BoneWeights2.ToArray();
                }
            }
            else if (builder is ArrayBuilder buildArray)
            {
                if (buildArray.Vertices == null)
                {
                    SysConsole.Output(OutputType.ERROR, "Failed to generate VBO, null vertices!");
                    return;
                }
                vecs = buildArray.Vertices;
                inds = buildArray.Indices;
                norms = buildArray.Normals;
                texs = buildArray.TexCoords;
                tangs = buildArray.Tangents ?? TangentsFor(vecs, norms, texs);
                cols = buildArray.Colors;
                if (buildArray.BoneIDs != null)
                {
                    Internal.HasBones = true;
                    ids = buildArray.BoneIDs;
                    weights = buildArray.BoneWeights;
                    ids2 = buildArray.BoneIDs2;
                    weights2 = buildArray.BoneWeights2;
                }
            }
            else
            {
                SysConsole.Output(OutputType.ERROR, "Failed to generate VBO, unknown builder type '" + builder + "'!");
                return;
            }
            if (vecs.Length == 0)
            {
                SysConsole.Output(OutputType.ERROR, "Failed to generate VBO, empty vertices!");
                return;
            }
#if DEBUG
            if (vecs.Length != norms.Length || vecs.Length != texs.Length || vecs.Length != tangs.Length)
            {
                SysConsole.Output(OutputType.ERROR, "Failed to generate VBO, main vertex attribute counts not aligned!");
                return;
            }
            if (cols != null && vecs.Length != cols.Length)
            {
                SysConsole.Output(OutputType.ERROR, "Failed to generate VBO, vertex color attribute count not aligned!");
                return;
            }
            if (Internal.HasBones && (vecs.Length != ids.Length || vecs.Length != weights.Length || vecs.Length != ids2.Length || vecs.Length != weights2.Length))
            {
                SysConsole.Output(OutputType.ERROR, "Failed to generate VBO, vertex bone attribute counts not aligned!");
                return;
            }
#endif
            Internal.IndexCount = inds.Length;
            Internal.LastVRAM = 0;
            // Vertex buffer
            GL.GenBuffers(1, out Internal.VertexVBO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.VertexVBO);
            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(vecs.Length * Vector3.SizeInBytes), vecs, Internal.BufferMode);
            Internal.LastVRAM += vecs.Length * Vector3.SizeInBytes;
            // Normal buffer
            GL.GenBuffers(1, out Internal.NormalVBO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.NormalVBO);
            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(norms.Length * Vector3.SizeInBytes), norms, Internal.BufferMode);
            Internal.LastVRAM += norms.Length * Vector3.SizeInBytes;
            // TexCoord buffer
            GL.GenBuffers(1, out Internal.TexCoordVBO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.TexCoordVBO);
            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(texs.Length * Vector3.SizeInBytes), texs, Internal.BufferMode);
            Internal.LastVRAM += texs.Length * Vector3.SizeInBytes;
            GL.GenBuffers(1, out Internal.TangentVBO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.TangentVBO);
            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(tangs.Length * Vector3.SizeInBytes), tangs, Internal.BufferMode);
            Internal.LastVRAM += tangs.Length * Vector3.SizeInBytes;
            // Color buffer
            if (cols != null)
            {
                Internal.HasColors = true;
                GL.GenBuffers(1, out Internal.ColorVBO);
                GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.ColorVBO);
                GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(cols.Length * Vector4.SizeInBytes), cols, Internal.BufferMode);
                Internal.LastVRAM += cols.Length * Vector4.SizeInBytes;
            }
            if (Internal.HasBones)
            {
                // Weight buffer
                GL.GenBuffers(1, out Internal.BoneWeightVBO);
                GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.BoneWeightVBO);
                GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(weights.Length * Vector4.SizeInBytes), weights, Internal.BufferMode);
                Internal.LastVRAM += weights.Length * Vector4.SizeInBytes;
                // ID buffer
                GL.GenBuffers(1, out Internal.BoneIDVBO);
                GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.BoneIDVBO);
                GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(ids.Length * Vector4.SizeInBytes), ids, Internal.BufferMode);
                Internal.LastVRAM += ids.Length * Vector4.SizeInBytes;
                // Weight2 buffer
                GL.GenBuffers(1, out Internal.BoneWeight2VBO);
                GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.BoneWeight2VBO);
                GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(weights2.Length * Vector4.SizeInBytes), weights2, Internal.BufferMode);
                Internal.LastVRAM += weights2.Length * Vector4.SizeInBytes;
                // ID2 buffer
                GL.GenBuffers(1, out Internal.BoneID2VBO);
                GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.BoneID2VBO);
                GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(ids2.Length * Vector4.SizeInBytes), ids2, Internal.BufferMode);
                Internal.LastVRAM += ids2.Length * Vector4.SizeInBytes;
            }
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            // Index buffer
            GL.GenBuffers(1, out Internal.IndexVBO);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, Internal.IndexVBO);
            GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(inds.Length * sizeof(uint)), inds, Internal.BufferMode);
            Internal.LastVRAM += inds.Length * sizeof(uint);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
            // VAO
            GL.GenVertexArrays(1, out Internal.VAO);
            GL.BindVertexArray(Internal.VAO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.VertexVBO);
            GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 0, 0);
            GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.NormalVBO);
            GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, 0, 0);
            GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.TexCoordVBO);
            GL.VertexAttribPointer(2, 3, VertexAttribPointerType.Float, false, 0, 0);
            GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.TangentVBO);
            GL.VertexAttribPointer(3, 3, VertexAttribPointerType.Float, false, 0, 0);
            if (Internal.HasColors)
            {
                GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.ColorVBO);
                GL.VertexAttribPointer(4, 4, VertexAttribPointerType.Float, false, 0, 0);
            }
            if (Internal.HasBones)
            {
                GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.BoneWeightVBO);
                GL.VertexAttribPointer(5, 4, VertexAttribPointerType.Float, false, 0, 0);
                GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.BoneIDVBO);
                GL.VertexAttribPointer(6, 4, VertexAttribPointerType.Float, false, 0, 0);
                GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.BoneWeight2VBO);
                GL.VertexAttribPointer(7, 4, VertexAttribPointerType.Float, false, 0, 0);
                GL.BindBuffer(BufferTarget.ArrayBuffer, Internal.BoneID2VBO);
                GL.VertexAttribPointer(8, 4, VertexAttribPointerType.Float, false, 0, 0);
            }
            GL.EnableVertexAttribArray(0);
            GL.EnableVertexAttribArray(1);
            GL.EnableVertexAttribArray(2);
            GL.EnableVertexAttribArray(3);
            if (cols != null)
            {
                GL.EnableVertexAttribArray(4);
            }
            if (Internal.HasBones)
            {
                GL.EnableVertexAttribArray(5);
                GL.EnableVertexAttribArray(6);
                GL.EnableVertexAttribArray(7);
                GL.EnableVertexAttribArray(8);
            }
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, Internal.IndexVBO);
            // Clean up
            GL.BindVertexArray(0);
            Generated = true;

        }

        /// <summary>
        /// Renders the VBO fully, without handling textures at all.
        /// </summary>
        /// <param name="context">The sourcing render context.</param>
        public void RenderWithoutTextures(RenderContext context)
        {
            if (!Generated)
            {
                return;
            }
            if (context != null)
            {
                context.ObjectsRendered++;
                context.VerticesRendered += Internal.IndexCount;
            }
            GL.BindVertexArray(Internal.VAO);
            GL.DrawElements(PrimitiveType.Triangles, Internal.IndexCount, DrawElementsType.UnsignedInt, IntPtr.Zero);
            GL.BindVertexArray(0);
        }

        /// <summary>
        /// Render the VBO fully.
        /// </summary>
        /// <param name="context">The sourcing render context.</param>
        /// <param name="fixafter">Whether to fix textures after rendering (if textures are enabled).</param>
        public void Render(RenderContext context, bool fixafter)
        {
            if (Tex != null)
            {
                GL.ActiveTexture(TextureUnit.Texture3);
                if (Tex_Reflectivity != null)
                {
                    Tex_Reflectivity.Bind();
                }
                else
                {
                    Tex.Engine.Black.Bind();
                }
                GL.ActiveTexture(TextureUnit.Texture2);
                if (Tex_Specular != null)
                {
                    Tex_Specular.Bind();
                }
                else
                {
                    Tex.Engine.Black.Bind();
                }
                GL.ActiveTexture(TextureUnit.Texture1);
                if (Tex_Normal != null)
                {
                    Tex_Normal.Bind();
                }
                else
                {
                    Tex.Engine.NormalDef.Bind();
                }
                GL.ActiveTexture(TextureUnit.Texture0);
                Tex.Bind();
            }
            RenderWithoutTextures(context);
            if (fixafter && Tex != null)
            {
                GL.ActiveTexture(TextureUnit.Texture3);
                Tex.Engine.Black.Bind();
                GL.ActiveTexture(TextureUnit.Texture2);
                Tex.Engine.Black.Bind();
                GL.ActiveTexture(TextureUnit.Texture1);
                Tex.Engine.NormalDef.Bind();
                GL.ActiveTexture(TextureUnit.Texture0);
                Tex.Engine.White.Bind();
            }
        }
    }

    /// <summary>
    /// Represents a set of texture coordinates.
    /// </summary>
    public class TextureCoordinates
    {
        /// <summary>
        /// Construct a basic set of texture coordinates.
        /// </summary>
        public TextureCoordinates()
        {
            XScale = 1;
            YScale = 1;
            XShift = 0;
            YShift = 0;
            XFlip = false;
            YFlip = false;
        }

        /// <summary>
        /// The X-Scale.
        /// </summary>
        public float XScale;

        /// <summary>
        /// The Y-Scale.
        /// </summary>
        public float YScale;

        /// <summary>
        /// The X-Shift.
        /// </summary>
        public float XShift;

        /// <summary>
        /// The Y-Shift.
        /// </summary>
        public float YShift;

        /// <summary>
        /// The X-flip option.
        /// </summary>
        public bool XFlip;

        /// <summary>
        /// The Y-flip option.
        /// </summary>
        public bool YFlip;

        /// <summary>
        /// Gets a quick string form of the Texture Coordinates.
        /// </summary>
        /// <returns>The string form.</returns>
        public override string ToString()
        {
            return XScale + "/" + YScale + "/" + XShift + "/" + YShift + "/" + (XFlip ? "t" : "f") + "/" + (YFlip ? "t" : "f");
        }

        /// <summary>
        /// Converts a quick string of a Texture Coordinate set to an actual TextureCoordinates.
        /// </summary>
        /// <param name="str">The string.</param>
        /// <returns>The texture coordinates.</returns>
        public static TextureCoordinates FromString(string str)
        {
            TextureCoordinates tc = new TextureCoordinates();
            string[] data = str.SplitFast('/');
            tc.XScale = StringConversionHelper.StringToFloat(data[0]);
            tc.YScale = StringConversionHelper.StringToFloat(data[1]);
            tc.XShift = StringConversionHelper.StringToFloat(data[2]);
            tc.YShift = StringConversionHelper.StringToFloat(data[3]);
            tc.XFlip = data[4] == "t";
            tc.YFlip = data[5] == "t";
            return tc;
        }
    }
}
